// 1.获取文件内容
//转换文件 将其他类型的的文件转化为js

/** 
 * 2.获取文件依赖关系 
 * ast -> 抽象语法树 将文件以语法树的形式呈现 需要下载依赖npm i @babel/parser
 * */

/** 
 * 3.将同扩展名的文件生成图 即队列queue
 * */

/** 
 * 3.根据生成图生成 整合了的 js 包 
 * npm i ejs 引入模板生成器 生成打包输出的文件的结构 
 * npm i babel-core 进行代码转换
 * npm i babel-preset-env 辅助代码转换的工具
 */

/** 
 *进行模块分割 给予每一个模块唯一的id
 *  
 */

import fs from 'fs' //需要在package.json声明 "type": "module",才可以使用
import path from 'path'
import ejs from 'ejs'
import parser from '@babel/parser'//依赖树生成工具
import traverse from '@babel/traverse' //对依赖树进行相关操作
import { transformFromAst } from 'babel-core' //这里是进行代码转换 函数里不能使用 import 需要替换成 require 我们打包出来的bundle是用函数将其他文件包裹住的,所以需要转换
import { jsonLoader } from './jsonLoader.js'
import { ChangeOutputPath } from './ChangeOutputPath.js'
import { SyncHook } from 'tapable'


let id = 0


//这里是可以独自用一个文件分割出去
const webpackConfig = {
    module: {
        rules: [{
            test: /\.json$/,
            use: [jsonLoader]
        }]
    },

    plugins: [new ChangeOutputPath()]
}

const hooks = {
    emitFile: new SyncHook(["context"])
}

//单个文件的解析 
function createAsset(filePath) {
    // 1.获取文件内容
    let source = fs.readFileSync(filePath, {
        encoding: 'utf-8'//输出字符串
    })//读取文件

    //转换文件格式为js 
    const loaders = webpackConfig.module.rules
    const loaderContext = {
        addDeps(dep) {
            console.log('addDeps', dep);
        }
    }

    loaders.forEach(({ test, use }) => {
        if (test.test(filePath)) {
            if (Array.isArray(use)) {
                use.forEach(fn => {
                    source = fn.call(loaderContext, source) //如果匹配成功则调用use里的函数
                })
            }

        }
    })

    // console.log(source);
    //2.获取文件依赖关系 
    const ast = parser.parse(source, {
        sourceType: "module",
    })
    //console.log(ast);
    let deps = []
    //解析树 获取依赖文件的路径
    traverse.default(ast, {
        //ImportDeclaration 是一个递归函数或者一个for循环?
        ImportDeclaration({ node }) {
            //console.log(node.source.value);//输出foo.js等等
            deps.push(node.source.value)
            //console.log(deps);
        }
    })

    const { code } = transformFromAst(ast, null, {
        presets: ["env"]
    })

    //console.log(code);

    return {
        filePath,
        code,
        deps,
        mapping: {},
        id: id++,
    }
}

// const asset = createAsset()

// console.log(asset);

/* 生成图 这里其实用的是递归 从入口文件开始解析每一个文件 获取依赖文件路径数组deps和当前文件内容source,然后将其放到队列里面,等待遍历deps的路径,形成递归,最后向下查找直到查找不到结束 */
function createGraph() {
    //这里应该也可以做一个配置文件 然后获取自定义的入口文件路径 没有就选默认的之类的
    const mainAsset = createAsset('./example/main.js')//入口文件

    const queue = [mainAsset] //创建队列 向下执行 找到新的依赖放进队列继续查找 真妙啊

    for (const asset of queue) {
        asset.deps.forEach(relativePath => {
            const child = createAsset(path.resolve('./example', relativePath))
            asset.mapping[relativePath] = child.id
            queue.push(child)
        })
    }
    return queue
}

//引入插件 注册hooks
function initPlugins() {
    const plugins = webpackConfig.plugins
    //plugins 为数组 由各种插件暴露的接口组成 插件规范是插件里需要有apply函数来注册hooks
    plugins.forEach((plugin) => {
        plugin.apply(hooks)
    })
}
initPlugins()

const graph = createGraph()

//生成模板
function build(graph) {
    const template = fs.readFileSync("./bundle.ejs", {
        encoding: 'utf-8'//输出字符串
    })
    const data = graph.map((asset) => {
        const { id, code, mapping } = asset
        return {
            id,
            code,
            mapping,
        }
    })
    //console.log(data);

    const code = ejs.render(template, { data }) //template内使用//使用<%- %> 语法 来引入data 具体使用在ejs文档
    let outputPath = "./dist/bundle.js"
    const context = {
        ChangeOutputPath(path) {
            outputPath = path
        }
    }
    hooks.emitFile.call(context)
    fs.writeFileSync(outputPath, code)

}

build(graph)